/*
 *  Camera.cpp
 *  Ray Tracing
 *
 *  Created by Jean-Marc Comby + Romain Dequesne on Fri May 14 2004.
 *  Copyright (c) 2004. All rights reserved.
 *
 *  Antialiasing par Johan Luisier (c) 2005
 *
 *  Prises de vue
 */

#include "Camera.hpp"

#include "Capteur.hpp"
#include "Cellule.hpp"

#include "Materiaux/Melange.hpp"

#include "Image/ImageFileTiff.hpp"

#include "Utils/ATL.hpp"
#include "Utils/debugstreams.hpp"

#include "Xml/ElementDOM.hpp"

#include <cmath>

#include <iomanip>
#include <boost/assign/list_of.hpp>

using namespace std;
using namespace boost;
using namespace boost::assign;

const double Camera::eps = 0.45;

/**
 * Permet de créer une Camera, sans pour autant déterminer les valeurs
 * clefs.
 *
 * @todo Enlever ce constructeur.
 */
Camera::Camera()
{
  Anti=non;
}

/**
 * Permet de créer une Camera en initialisant tous les champs.
 *
 * @param pos position de la Camera.
 * @param dir direction de la prise de vue.
 * @param haut direction de la hauteur.
 * @param dist distance focale.
 * @param ecran pointeur sur l'image à créer.
 * @param env pointeur sur la scène à rendre.
 * @param pre profondeur de récursion.
 * @param seuil seuil de détection des pixels à antialiaser.
 * @param alias type d'antialiasing.
 */
Camera::Camera(const Coord & pos, const VecteurU& dir, const VecteurU& haut,
	       const double& dist, Image *ecran, Environnement* env,
	       unsigned const int& pre, const double& seuil, const int& alias)
  : Position(pos)
  , Direction(dir),
    Hauteur(haut)
  , Largeur(Hauteur^Direction)
  , Distance( dist )
  , Env(env)
  , Precision(pre)
  , Seuil( seuil )
  , Compteur( 0 )
  , NbrPasses( 1 )
  , FacteurOversampling( 1 )
  , RenduPrimaire( true )
  , Cellules()
  , BarreProgression( 0 )
{
  Film = ecran;
  SuperFilm = 0;
  vector< bool > tmp( Film -> largeur(), false );
  Contour.assign( Film -> hauteur(), tmp );
  switch (alias)
    {
    case 0:
      Anti=non;
      break;
    case 1:
      Anti=simple;
      break;
    case 2:
      Anti=bourrin;
      break;
    case 3:
      Anti=ultra_bourrin;
      break;
    default:
      Anti=non;
    }

  unsigned i, j;

  for ( j = 0u; j < Film -> largeur(); ++j )
  {
      for ( i = 0u; i < Film -> hauteur(); ++i )
          Capteurs.push_back( new Capteur( i, j, this ) );
  }
}

/** Destructeur, libère la mémoire allouée dynamiquement.
 *
 * Le destructeur doit libérer la mémoire allouée pour SuperFilm (dans
 * le cas où FacteurOversampling est @f$ > 1@f$), ainsi que les
 * différentes instances de Cellule.
 */
Camera::~Camera()
{

    if ( Anti != non )
    {
        double num(Compteur), denom( Film->largeur() * Film->hauteur() );
        char pourcent[50];
        denom *= double( NbrPasses );
        sprintf( pourcent, "%5.2f %%", num / denom * 100. );
        cout << "Pixels antialiasés : " << pourcent << " ("
             << Compteur << '/'
             << Film->largeur() * Film->hauteur() * NbrPasses
             << ")" << endl;
    }

  if ( FacteurOversampling > 1 )
    delete SuperFilm;

  while ( ! Capteurs.empty() )
  {
      delete Capteurs.back();
      Capteurs.pop_back();
  }

  while ( ! Cellules.empty() )
    {
      delete Cellules.back();
      Cellules.pop_back();
    }
}

/**
 * Réalise le rendu de la scène.
 */
void Camera::genereImage()
{
    cout<<"Lancer de rayon\n"<<"Patientez"<<endl << flush;
    unsigned hauteur( Film->hauteur() ), largeur( Film->largeur() );
    //double FocaleEffective( Distance * double( FacteurOversampling ) );

    vector< Capteur* >::iterator capIt, capEnd( Capteurs.end() );

    for ( capIt = Capteurs.begin(); capIt != capEnd; ++capIt )
        (*capIt) -> generePixel();

    if ( Anti != non )
    {
        for ( unsigned passe( 0u ); passe < NbrPasses; ++passe )
        {
            determinerContour();
            RenduPrimaire = false;
            for ( capIt = Capteurs.begin(); capIt != capEnd; ++capIt )
                (*capIt) -> generePixel();
        }
    }

    return;

    if ( FacteurOversampling == 1 )
        SuperFilm = Film;
    else
    {
        largeur *= FacteurOversampling;
        hauteur *= FacteurOversampling;
        SuperFilm = new Image( largeur, hauteur );
    }

    long loops( largeur * hauteur ); // nombre de pixel total
    BarreProgression = new progress_display( loops ); // affiche la barre de progression
    progress_timer *t = new progress_timer(); // demarre le chronometre

    Cellules.push_back( new Cellule( this ) );
    Cellules.back() -> genereCellule();
    /*
  for(unsigned i(0), j;i<hauteur;i++)
    {
      for(j = 0;j<largeur;j++)
    {
      int x( largeur/2 - j );
      int y( hauteur/2 - i );

      VecteurU v( FocaleEffective * Direction +
              double(x) * Largeur + double(y) * Hauteur );
      Rayon ray( Position, v );
      SuperFilm -> setPixel( i, j, Env->lanceRayon( ray, Precision, 1.0 ) );
      ++pd;
    }
    }
  */

    delete BarreProgression;
    BarreProgression = 0;


    if ( FacteurOversampling > 1 )
    {
        vector< double > poids( FacteurOversampling * FacteurOversampling,
                                1. / double( FacteurOversampling * FacteurOversampling ) );
        vector< Couleur > coul;

        largeur /= FacteurOversampling;
        hauteur /= FacteurOversampling;

        for(unsigned i(0), j;i<hauteur;i++)
        {
            for(j = 0;j<largeur;j++)
            {
                reduireImage( i * FacteurOversampling,
                              j * FacteurOversampling, coul );
                if ( coul.size() != poids.size() )
                    cerr << "Problème" << endl;
                Film -> setPixel( i, j, faireMelangeCouleurs( coul, poids ) );
            }
        }

        delete SuperFilm;
    }

    cout << "Temps ecoule pour le rendu primaire : ";
    //donne le temps a la destruction de t
    delete t;

    if ( Anti != non )
        postRendu();
}

/**
 * Permet de modifier la direction de la prise de vue.
 *
 * @param a nouvelle valeur de la direction.
 */
void Camera::setDirection(const Vecteur & a)
{
  Direction = a;
}

/**
 * Permet de modifier la distance focale.
 *
 * @param a nouvelle valeur de la distance focale.
 */
void Camera::setDistance(const double& a)
{
  Distance=a;
}

/**
 * Permet d'accéder à la distance focale de la caméra.
 *
 * @return une référence constante sur Distance.
 */
const double& Camera::distance() const
{
  return Distance;
}

/**
 * Permet de modifier la scène.
 *
 * @param a pointeur sur la scène.
 */
void Camera::env(Environnement* a)
{
  Env=a;
}

/**
 * Permet de modifier la direction de la hauteur.
 *
 * @param a nouvelle valeur de la hauteur.
 */
void Camera::setHauteur(const Vecteur& a)
{
  Hauteur=a;
}

/**
 * Permet d'accéder à la hauteur de la caméra (orientation de la
 * verticale).
 *
 * @return une référence constante sur Hauteur.
 */
const VecteurU& Camera::hauteur() const
{
  return Hauteur;
}

/**
 * Permet de mettre à jour la valeur de la largeur.
 */
void Camera::setLargeur()
{
  Largeur= Hauteur ^ Direction;
}

/**
 * Permet d'accéder à la largeur (orientation horizontale de la
 * caméra).
 *
 * @return une référence constante sur Largeur.
 */
const VecteurU& Camera::largeur() const
{
  return Largeur;
}

/**
 * Permet de modifier la position de la Camera.
 *
 * @param a nouvelle valeur de la position.
 */
void Camera::setPosition(const Coord& a)
{
  Position = a;
}

/**
 * Permet d'accéder à la position de la caméra-
 *
 * @return une référence constante sur Position.
 */
const Coord& Camera::position() const
{
  return Position;
}

/**
 * Permet d'accéder à la direction de la caméra.
 *
 * @return une référence constante sur Direction.
 */
const VecteurU& Camera::direction() const
{
  return Direction;
}

/**
 * Permet de modifier la profondeur de récursion à utiliser pour
 * générer la scène.
 *
 * @param a nouvelle valeur de la profondeur de récursion.
 */
void Camera::setPrecision(const unsigned& a)
{
  Precision = a;
}

/**
 * Permet d'accéder à la profondeur de récursion de la caméra.
 *
 * @return une référence constante sur Precision.
 */
const unsigned& Camera::precision() const
{
  return Precision;
}

/**
 * Permet de modifier l'écran sur lequel l'Image sera projetée.
 *
 * @param a pointeur sur la nouvelle Image.
 */
void Camera::setFilm(Image *a)
{
  Film=a;
}

/**
 * Permet de récupérer le pointeur sur l'image à générer.
 *
 * @return le pointeur Film.
 */
Image* Camera::film()
{
  return Film;
}

/**
 * Permet de modifier le type d'antialiasing utilisé.
 *
 * @param alias nouvelle valeur de l'antialising.
 */
void Camera::setAlias(const unsigned & alias)
{
  switch (alias)
    {
    case 0:
      Anti=non;
      break;
    case 1:
      Anti=simple;
      break;
    case 2:
      Anti=bourrin;
      break;
    case 3:
      Anti=ultra_bourrin;
      break;
    default:
      Anti=non;
    }
}

/**
 * Permet d'accéder au type d'antialiasing à appliquer.
 *
 * @return une référence constante sur Anti.
 */
const Antialiasing& Camera::alias() const
{
  return Anti;
}

/**
 * Permet de changer le nombre de passes d'antialiasing à effectuer.
 *
 * La valeur est automatiquement bornée dans l'interval \f$ \left[ 1,
 * \right] \f$.
 *
 * @param[in] nbr nombre de passes à effectuer.
 */
void Camera::setNbrPasses( const unsigned& nbr )
{
  NbrPasses = max( 1u, min( nbr, 5u ) );
}

/**
 * Permet d'accéder au nombre de passes d'antialiasing à effectuer.
 *
 * @return une référence constante sur NbrPasses.
 */
const unsigned& Camera::nbrPasses() const
{
  return NbrPasses;
}

/**
 * Permet de changer la valeur du facteur d'oversampling.
 *
 * Ce facteur est automatiquemt borné dans l'intervalle \f$ \left[ 1,
 * 4\right] \f$.
 *
 * @param[in] facteur nouvelle valeur du facteur d'oversampling.
 */
void Camera::setFacteurOversampling( const unsigned& facteur )
{
  FacteurOversampling = max( 1u, min( facteur, 4u ) );
}

/**
 * Permet d'accéder au facteur d'oversampling.
 *
 * @return une référence constante sur FacteurOversampling.
 */
const unsigned& Camera::facteurOversampling() const
{
  return FacteurOversampling;
}

/**
 * Analyse l'image créée par genereImage() et calcule le contraste
 * entre chaque pixel et ses 8 plus proches voisins. Si le contraste
 * est en dessus d'un certain seuil, alors on va antialiaser le pixel
 * concerné.
 *
 * Pour éviter les problèmes de calculs lorsque le pixel est dans le
 * bord de l'image, on ne fait cette opération que pour les pixel qui
 * sont à une distance minimum de 1 du bord de l'image.
 *
 * L'opérateur utilisé pour calculer le contraste est l'opérateur de
 * Sobel~:
 *
 * \f[ \mathcal{S} = \left\vert c_1 + 2 \, c_2 + c_3 - \left( c_7 + 2
 * \, c_8 + c_9 \right) \right\vert + \left\vert c_3 + 2 \, c_6 + c_9
 * - \left( c_1 + 2 \, c_4 + c_7 \right) \right\vert \f]
 *
 * Si la valeur de cet opérateur est supérieur à la valeur de Seuil,
 * alors on antialiase le pixel.
 */
void Camera::postRendu()
{
    cout << "Post-rendu" << endl << "Patientez" << endl;

    long loops( ( Film -> hauteur() - 2 ) * ( Film -> largeur() - 2 ) * NbrPasses );

    BarreProgression = new progress_display( loops ); // affiche la barre de progression
    progress_timer *t = new progress_timer(); // demarre le chronometre


    Cellules.back() -> rMinX()++;
    Cellules.back() -> rMaxX()--;
    Cellules.back() -> rMinY()++;
    Cellules.back() -> rMaxY()--;
    Cellules.back() -> setRenduPrimaire( false );

    for ( unsigned passe( 0u ); passe < NbrPasses; ++passe )
    {
        determinerContour();
        Cellules.back() -> genereCellule();
    }

    /*
  Couleur col;
  vector< VecteurU > directions;
  vector< Rayon > rayons;
  vector< Couleur > couleurs;
  
  vector< double > poids;
  
  vector< vector< bool > > contour;

  if ( Anti == simple )
    poids = list_of( .25 )( .25 )( .25 )( .25 );
  else if ( Anti == bourrin )
    poids = list_of( .2 )( .2 )( .2 )( .2 )( .2 );
  else if ( Anti == ultra_bourrin )
    poids = list_of( .1 )( .075 )( .075 )( .075 )( .075 )
      ( .075 )( .075 )( .075 )( .075 )( .075 )( .075 )( .075 )( .075 );

  double x, y;

  unsigned i, j, k, largeur( Film -> largeur() ),
    hauteur( Film -> hauteur() ), nombre( poids.size() ), passe( 0 );
  long loops( (largeur - 2)*(hauteur - 2) * NbrPasses ); // nombre de pixel total

  for ( ; passe < NbrPasses; passe++ )
    {
      determinerContour( contour );

      for(i = 1; i < hauteur - 1; i++)
    {
      for(j = 1; j < largeur - 1; j++)
        {
          ++pd;

          if ( contour[ i ][ j ] )
        Compteur++;
          else
        {
          continue;
        }

          x = static_cast<double>(largeur/2)-
        static_cast<double>(j);
          y = static_cast<double>(hauteur/2)-
        static_cast<double>(i);

          directions.clear();
          rayons.clear();
          couleurs.clear();

          determinerDirections( directions, x, y );

          for ( k = 0; k < nombre; k++ )
        {
          rayons.push_back( Rayon( Position, directions[ k ] ) );
          couleurs.push_back( Env->lanceRayon( rayons[ k ],
                               Precision, 1.0 ) );
        }

          col = faireMelangeCouleurs( couleurs, poids );
          Film -> setPixel( i, j, col );

        }
    }
    }
  */

    delete BarreProgression;
    BarreProgression = 0;

    cout << "Temps écoulé pour le post-traitement : ";
    delete t;
    t = 0;
}

/**
 * Détermine la liste des pixel à antialiaser. On parcours les pixels
 * de l'image (sauf le périmètre) et on évalue l'opérateur de
 * Sobel. Si la valeur est supérieure à Camera::Seuil, alors ce pixel
 * sera antialiasé.
 *
 * param[out] contour tableau contenant \e true à la position \f$ [ i,
 * j ] \f$ si le pixel \f$ [ i, j ] \f$ a un constraste trop important
 * avec ses voisins, \e false sinon.
 */
void Camera::determinerContour()
{
    Couleur c1, c2, c3, c4, c5, c6, c7, c8, c9;

    unsigned i, j, hauteur( Film -> hauteur() ), largeur( Film -> largeur() );

    vector< bool > tmp( largeur, false );
    Contour.assign( hauteur, tmp );

    double sobel;

    for(i = 1; i < hauteur - 1; i++)
    {
        for(j = 1; j < largeur - 1; j++)
        {
            c1 = Film -> pixel( i - 1, j - 1 );
            c2 = Film -> pixel( i - 0, j - 1 );
            c3 = Film -> pixel( i + 1, j - 1 );
            c4 = Film -> pixel( i - 1, j - 0 );
            c5 = Film -> pixel( i - 0, j - 0 );
            c6 = Film -> pixel( i + 1, j - 0 );
            c7 = Film -> pixel( i - 1, j + 1 );
            c8 = Film -> pixel( i - 0, j + 1 );
            c9 = Film -> pixel( i + 1, j + 1 );

            sobel =
                    pow(
                        pow( ( c1.intensite() +
                               2 * c2.intensite() +
                               c3.intensite() ) -
                             ( c7.intensite() +
                               2 * c8.intensite() +
                               c9.intensite() ), 2. )
                        + pow ( ( c3.intensite() +
                                  2 * c6.intensite() +
                                  c9.intensite() ) -
                                ( c1.intensite() +
                                  2 * c4.intensite() +
                                  c7.intensite() ), 2. ),
                        .5 );
            /*
        std::abs( ( c1.intensite() +
        2 * c2.intensite() +
        c3.intensite() ) -
        ( c7.intensite() +
        2 * c8.intensite() +
        c9.intensite() )
        ) +
        std::abs( ( c3.intensite() +
        2 * c6.intensite() +
        c9.intensite() ) -
        ( c1.intensite() +
        2 * c4.intensite() +
        c7.intensite() )
        );
      */


            if ( sobel > Seuil )
                Contour[ i ][ j ] = true;
        }
    }
}

/**
 * Détermine la direction des rayons supplémentaires à lancer
 * (uniquement si \c Anti \f$ \ne \f$ \c non).
 *
 * @param[out] directions conteneur des directions des différents
 * rayons.
 * @param[in] x coordonnée x du "pixel".
 * @param[in] y coordonnée y du "pixel".
 */
void Camera::determinerDirections( std::vector< VecteurU >& directions,
				   const double& x, const double& y ) const
{
  if ( Anti == simple )
    {
      // On tire 4 rayons sur un cercle de rayon eps/2
      
      directions.push_back( VecteurU(Distance*Direction+(x+eps/2)*Largeur+y*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x-eps/2)*Largeur+y*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+x*Largeur+(y+eps/2)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+x*Largeur+(y-eps/2)*Hauteur) );
      /*
	for (k = 0; k < 4; k++)
	{
	rayons.push_back( Rayon( Position, directions[ k ] ) );
	couleurs.push_back( Env->lanceRayon( rayons[ k ],
	Precision, 1.0 ) );
	}
	
	col = faireMelangeCouleurs( couleurs, poids );
	Film -> setPixel( i, j, col );
      */
    }
  else if ( Anti == bourrin )
    {
      directions.push_back( VecteurU(Distance*Direction+x*Largeur+y*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x+eps)*Largeur+y*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x-eps)*Largeur+y*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+x*Largeur+(y+eps)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+x*Largeur+(y-eps)*Hauteur) );
      /*
	for (k = 0; k < 5; k++)
	{
	rayons.push_back( Rayon( Position, directions[ k ] ) );
	couleurs.push_back( Env->lanceRayon( rayons[ k ],
	Precision, 1.0 ) );
	}
		
	col = faireMelangeCouleurs( couleurs, poids );
	Film -> setPixel( i, j, col );
      */
    }
  else if ( Anti == ultra_bourrin )
    {
      /*
	Là on tire 13 rayons: le rayon original
	plus 8 rayons sur un cercle de rayon eps
	et les quatres rayons sur un cercle de rayon 1.414 eps
      */
	    
      directions.push_back( VecteurU(Distance*Direction+x*Largeur+y*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x+eps)*Largeur+y*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x-eps)*Largeur+y*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+x*Largeur+(y+eps)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+x*Largeur+(y-eps)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x+eps*.7071)*Largeur+(y+eps*.7071)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x-eps*.7071)*Largeur+(y-eps*.7071)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x-eps*.7071)*Largeur+(y+eps*.7071)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x+eps*.7071)*Largeur+(y-eps*.7071)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x+eps)*Largeur+(y+eps)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x-eps)*Largeur+(y-eps)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x-eps)*Largeur+(y+eps)*Hauteur) );
      directions.push_back( VecteurU(Distance*Direction+(x+eps)*Largeur+(y-eps)*Hauteur) );
      /*
	for (k = 0; k < 13; k++)
	{
	rayons.push_back( Rayon( Position, directions[ k ] ) );
	couleurs.push_back( Env->lanceRayon( rayons[ k ],
	Precision, 1.0 ) );
	}
		
	col = faireMelangeCouleurs( couleurs, poids );
	Film -> setPixel( i, j, col );
      */
    }

}

/**
 * Permet de réduire une image suréchantillonée à l'image cible. Pour
 * le pixel final donnée, les pixels sont collectés ici, pour être
 * moyennés.
 *
 * @param[in] iFinal coordonnée x de l'image finale.
 * @param[in] jFinal coordonnée y de l'image finale.
 * @param[out] couleurs conteneur des couleurs à moyenner pour obtenir
 * le pixel final.
 */
void Camera::reduireImage( const unsigned& iFinal, const unsigned& jFinal,
			   vector< Couleur >& couleurs ) const
{
  unsigned it1, it2, borne1( iFinal + FacteurOversampling ),
    borne2( jFinal + FacteurOversampling );

  couleurs.clear();

  for ( it1 = iFinal; it1 < borne1; it1++ )
    {
      for ( it2 = jFinal; it2 < borne2; it2++ )
	{
	  couleurs.push_back( SuperFilm -> pixel( it1, it2 ) );
	}
    }
}

/** Méthode qui incrémente la barre de progression.
 *
 * Cette méthode incrémente la barre de progresssion du rendu de
 * l'image.
 */
void Camera::incrementeProgression()
{
  ++( *BarreProgression );
}
